﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace Magellan.Utilities
{
    /// <summary>
    /// Helper class to return attributes from the buddy class as if they are on the actual class
    /// </summary>
    public static class BuddyClassAttributeProvider
    {
        /// <summary>
        /// Gets attributes and buddy class attributes
        /// </summary>
        /// <param name="type"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static IEnumerable<Attribute> GetForClass(Type type, object instance)
        {
            var buddyClassTypes = GetMetadataTypes(type, instance).ToArray();

            var allAttributes = new List<Attribute>();

            if (buddyClassTypes.Any())
            {
                foreach (var buddyTypeAttribute in buddyClassTypes)
                {
                    var typeAttributes =
                        TypeDescriptor.GetAttributes(buddyTypeAttribute.MetadataClassType);

                    allAttributes.AddRange(typeAttributes.OfType<Attribute>());
                }
            }

            allAttributes.AddRange(TypeDescriptor.GetAttributes(type).OfType<Attribute>());

            return allAttributes;
        }

        /// <summary>
        /// Gets the registered metadatatypes for a class
        /// </summary>
        /// <param name="type"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static IEnumerable<MetadataTypeAttribute> GetMetadataTypes(Type type, object instance)
        {
            var baseType = type;
            var allMetadataAttributesOnType = new List<MetadataTypeAttribute>();

            do
            {
                allMetadataAttributesOnType.AddRange(
                    TypeDescriptor.GetAttributes(baseType).OfType<MetadataTypeAttribute>());
                baseType = baseType.BaseType;
            } while (baseType != null);

            return allMetadataAttributesOnType;
        }


        /// <summary>
        /// Get attributes and buddy class attributes for a property
        /// </summary>
        /// <param name="type"></param>
        /// <param name="propertyName"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static IEnumerable<Attribute> GetForProperty(Type type, string propertyName, object instance)
        {
            return
                GetForProperty(
                    type,
                    TypeDescriptor.GetProperties(type).OfType<PropertyDescriptor>().First(pd => pd.Name == propertyName),
                    instance);
        }

        /// <summary>
        /// Get attributes and buddy class attributes for a property
        /// </summary>
        /// <param name="type"></param>
        /// <param name="propertyDescriptor"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static IEnumerable<Attribute> GetForProperty(Type type, PropertyDescriptor propertyDescriptor,
                                                            object instance)
        {
            var buddyClassTypes = GetMetadataTypes(type, instance).ToArray();

            var allAttributes = new List<Attribute>();

            if (buddyClassTypes.Any())
            {
                foreach (var buddyTypeAttribute in buddyClassTypes)
                {
                    var propertyInfo =
                        TypeDescriptor
                            .GetProperties(buddyTypeAttribute.MetadataClassType)
                            .OfType<PropertyDescriptor>()
                            .FirstOrDefault(pd => pd.Name == propertyDescriptor.Name);

                    if (propertyInfo != null)
                        allAttributes.AddRange(propertyInfo.Attributes.OfType<Attribute>());
                }
            }

            allAttributes.AddRange(propertyDescriptor.Attributes.OfType<Attribute>());

            return allAttributes;
        }
    }
}